//
//  CoGeBeatDetektorPlugIn.m
//  CoGeBeatDetektor
//
//  Created by Tamas Nagy on 6/11/11.
//  Copyright (c) 2011 home. All rights reserved.
//

/* It's highly recommended to use CGL macros instead of changing the current context for plug-ins that perform OpenGL rendering */
#import <OpenGL/CGLMacro.h>
#include <stdlib.h>
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#include <iostream>
#include <math.h>
#include <vector>
#include <map>
#include "FFT.h"
#include "BeatDetektor.h"
#include "Timer.h"

#import "CoGeBeatDetektorPlugIn.h"

#define	kQCPlugIn_Name				@"CoGeBeatDetektor"
#define	kQCPlugIn_Description		@"Detect BPM on default audio input"

BeatDetektor *det;

#define DET_POOL_SIZE 12
BeatDetektor *det_pool[DET_POOL_SIZE];


#define SRATE 44100
#define BUF 1024

using namespace std;

ALCdevice *audio_device;
ALbyte audio_buffer[SRATE];
ALint samples;

vector<float> sample_data;
vector<float> fft_data;
vector<float> ma_fft_data;
vector<float> maa_fft_data;
vector<float> spectrum_analysis;
float det_max_moving;

Timer visTimer;
bool bpm_latch = false;


// Capture audio sample (OpenAL) and compute FFT
static void capture(void)
{
	int x;
	
	int lval=32768;
	float bval = 0.0;
	
	alcGetIntegerv(audio_device, ALC_CAPTURE_SAMPLES, (ALCsizei)sizeof(ALint), &samples);
	alcCaptureSamples(audio_device, (ALCvoid *)audio_buffer, samples);
	
	for (x=0; x<BUF; x++)
	{
		bval = (((float)((ALint *)audio_buffer)[x])/32767.0) / (float)lval;
		sample_data[x]=bval;
	}
	
	fft_data = sample_data;
	
	DanielsonLanczos <(BUF/2), float> dlfft;
	
	unsigned long n, m, j, i;
	
	// reverse-binary reindexing
    n = (BUF/2)<<1;
    j=1;
    for (i=1; i<n; i+=2) {
        if (j>i) {
            swap(fft_data[j-1], fft_data[i-1]);
            swap(fft_data[j], fft_data[i]);
        }
        m = (BUF/2);
        while (m>=2 && j>m) {
            j -= m;
            m >>= 1;
        }
        j += m;
    };
	
	dlfft.apply(&fft_data[0]);
	
	float det_max = 0.0;
	
	for (i = 0; i < fft_data.size(); i++)
	{
			ma_fft_data[i] += (fabs(fft_data[i])-ma_fft_data[i])*BD_DETECTION_RATE*visTimer.lastUpdateSeconds()*2.0;
			maa_fft_data[i] += (ma_fft_data[i]-maa_fft_data[i])*BD_DETECTION_RATE*visTimer.lastUpdateSeconds()*2.0;
		
		float det_val = (ma_fft_data[i]/maa_fft_data[i]);	
		if (det_val > det_max) det_max = det_val;
	}
	
	if (det_max <= 0) det_max = 1.0;
	
	for (i = 0; i < fft_data.size(); i++)
	{
		float det_val = (ma_fft_data[i]/maa_fft_data[i]);
		
		if (det_val>1.5)
		{
			det_val -= 1.0;
			
			if (det_val > spectrum_analysis[i]) spectrum_analysis[i] = det_val;
				else if (det->current_bpm) spectrum_analysis[i] -= (spectrum_analysis[i]-det_val)*visTimer.lastUpdateSeconds()*(1.0/det->current_bpm)*3.0;
		}
		else 
		{
				if (det->current_bpm) spectrum_analysis[i] -= (visTimer.lastUpdateSeconds()/det->current_bpm)*2.0;
		}
		
		if (spectrum_analysis[i] < 0) spectrum_analysis[i] = 0;
		if (spectrum_analysis[i] > det_max_moving) spectrum_analysis[i] = det_max_moving;
	}
	
	if (det_max_moving == 0) det_max_moving = det_max;
		else det_max_moving += (det_max-det_max_moving)*visTimer.lastUpdateSeconds();
	
	if (!det_max_moving) det_max_moving = 1.0;
	
	//	fvals = dft(cvals);
	
}	


@implementation CoGeBeatDetektorPlugIn

@dynamic outputBPM;
@dynamic outputBeats;
@dynamic inputMinBPM;
@dynamic inputMaxBPM;


/*
Here you need to declare the input / output properties as dynamic as Quartz Composer will handle their implementation
@dynamic inputFoo, outputBar;
*/

+ (NSDictionary*) attributes
{
	/*
	Return a dictionary of attributes describing the plug-in (QCPlugInAttributeNameKey, QCPlugInAttributeDescriptionKey...).
	*/
	
	return [NSDictionary dictionaryWithObjectsAndKeys:kQCPlugIn_Name, QCPlugInAttributeNameKey, kQCPlugIn_Description, QCPlugInAttributeDescriptionKey, nil];
}

+ (NSDictionary*) attributesForPropertyPortWithKey:(NSString*)key
{
	/*
	Specify the optional attributes for property based ports (QCPortAttributeNameKey, QCPortAttributeDefaultValueKey...).
	*/
	
	if([key isEqualToString:@"inputMinBPM"])
	{
		return [NSDictionary dictionaryWithObjectsAndKeys:@"MinBPM", QCPortAttributeNameKey,
				[NSNumber numberWithDouble:85], QCPortAttributeDefaultValueKey,
				nil];
	}
	
	if([key isEqualToString:@"inputMaxBPM"])
	{
		return [NSDictionary dictionaryWithObjectsAndKeys:@"MaxBPM", QCPortAttributeNameKey,
				[NSNumber numberWithDouble:159], QCPortAttributeDefaultValueKey,
				nil];
	}
	
	return nil;
}

+ (QCPlugInExecutionMode) executionMode
{
	/*
	Return the execution mode of the plug-in: kQCPlugInExecutionModeProvider, kQCPlugInExecutionModeProcessor, or kQCPlugInExecutionModeConsumer.
	*/
	
	return kQCPlugInExecutionModeProcessor;
}

+ (QCPlugInTimeMode) timeMode
{
	/*
	Return the time dependency mode of the plug-in: kQCPlugInTimeModeNone, kQCPlugInTimeModeIdle or kQCPlugInTimeModeTimeBase.
	*/
	
	return kQCPlugInTimeModeIdle;
}

- (id) init
{
	if(self = [super init]) {
		/*
		Allocate any permanent resource required by the plug-in.
		*/
	}
	
	return self;
}

- (void) finalize
{
	/*
	Release any non garbage collected resources created in -init.
	*/
	
	[super finalize];
}

- (void) dealloc
{
	/*
	Release any resources created in -init.
	*/
	
	[super dealloc];
}

@end

@implementation CoGeBeatDetektorPlugIn (Execution)

- (BOOL) startExecution:(id<QCPlugInContext>)context
{
	/*
	Called by Quartz Composer when rendering of the composition starts: perform any required setup for the plug-in.
	Return NO in case of fatal failure (this will prevent rendering of the composition to start).
	*/
	
	alGetError();
	const ALchar *pDeviceList = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
	if (pDeviceList) {
		printf("Available Capture Devices are:\n");
		while (*pDeviceList) {
			printf("**%s**\n", pDeviceList);
			pDeviceList += strlen(pDeviceList) + 1;
		}
	}
	
	audio_device = alcCaptureOpenDevice(NULL, SRATE*2, AL_FORMAT_STEREO16, BUF);
	if (alGetError() != AL_NO_ERROR)
	{
		return NO;
	}
	alcCaptureStart(audio_device);

	sample_data.resize(BUF);
	fft_data.resize(BUF);
	ma_fft_data.resize(BUF);
	maa_fft_data.resize(BUF);
	spectrum_analysis.resize(BUF);
	
	
	return YES;
}

- (void) enableExecution:(id<QCPlugInContext>)context
{
	/*
	Called by Quartz Composer when the plug-in instance starts being used by Quartz Composer.
	*/
}

- (BOOL) execute:(id<QCPlugInContext>)context atTime:(NSTimeInterval)time withArguments:(NSDictionary*)arguments
{
	/*
	Called by Quartz Composer whenever the plug-in instance needs to execute.
	Only read from the plug-in inputs and produce a result (by writing to the plug-in outputs or rendering to the destination OpenGL context) within that method and nowhere else.
	Return NO in case of failure during the execution (this will prevent rendering of the current frame to complete).
	
	The OpenGL context for rendering can be accessed and defined for CGL macros using:
	CGLContextObj cgl_ctx = [context CGLContextObj];
	*/
	
	if (([self didValueForInputKeyChange:@"inputMinBPM"]) || ([self didValueForInputKeyChange:@"inputMaxBPM"])) {
		
		visTimer.stop();
		
		det = new BeatDetektor(self.inputMinBPM,self.inputMaxBPM);
		
		visTimer.start();
		
	}
	
	visTimer.update();
	
	capture();	
	
	det->process(visTimer.getSeconds(),fft_data);
	
	if (det->winning_bpm && det->win_bpm_int && ((det->bpm_contest[det->win_bpm_int] > 30.0 && !bpm_latch) || (det->bpm_contest[det->win_bpm_int] > 25.0 && bpm_latch)))
	{
		
	//	NSLog(@"current bpm: %0.1f",det->win_bpm_int/10.0);
		bpm_latch = true;
		self.outputBPM = det->win_bpm_int/10.0;
	}
	else
	{
	//	NSLog(@"searching...");
		bpm_latch = false;
	}
	
	self.outputBeats = det->beat_counter;
	
	return YES;
}

- (void) disableExecution:(id<QCPlugInContext>)context
{
	/*
	Called by Quartz Composer when the plug-in instance stops being used by Quartz Composer.
	*/

	alcCaptureStop(audio_device);
	alcCaptureCloseDevice(audio_device);
	visTimer.stop();
}

- (void) stopExecution:(id<QCPlugInContext>)context
{
	/*
	Called by Quartz Composer when rendering of the composition stops: perform any required cleanup for the plug-in.
	*/
	

}



@end
